ace = require 'ace'
Range = ace.require('ace/range').Range

# This class can either wrap an AetherProblem,
# or act as a general runtime error container for web-dev iFrame errors.
# TODO: Use subclasses? Might need a factory pattern for that (bleh)
module.exports = class Problem
  annotation: null
  markerRange: null
  # Construction with AetherProblem will include all but `error`
  # Construction with a standard error will have `error`, `isCast`, `levelID`, `ace`
  constructor: ({ @aether, @aetherProblem, @ace, isCast=false, @levelID, error, userCodeHasChangedSinceLastCast }) ->
    if @aetherProblem
      @annotation = @buildAnnotationFromAetherProblem(@aetherProblem)
      { @lineMarkerRange, @textMarkerRange } = @buildMarkerRangesFromAetherProblem(@aetherProblem) if isCast

      { @level, @range, @message, @hint, @userInfo } = @aetherProblem
      { @row, @column: col } = @aetherProblem.range?[0] or {}
      @createdBy = 'aether'
      @message = @translate @message
      @hint = @translate @hint
    else
      unless userCodeHasChangedSinceLastCast
        @annotation = @buildAnnotationFromWebDevError(error)
        { @lineMarkerRange, @textMarkerRange } = @buildMarkerRangesFromWebDevError(error)

      @level = 'error'
      @row = error.line
      @column = error.column
      @message = error.message or 'Unknown Error'
      if error.line and not userCodeHasChangedSinceLastCast
        @message = "Line #{error.line + 1}: " + @message # Ace's gutter numbers are 1-indexed but annotation.rows are 0-indexed
      if userCodeHasChangedSinceLastCast
        @hint = "This error was generated by old code — Try running your new code first."
      else
        @hint = undefined
      @userInfo = undefined
      @createdBy = 'web-dev-iframe'
      # TODO: Include runtime/transpile error types depending on something?

    # TODO: get ACE screen line, too, for positioning, since any multiline "lines" will mess up positioning
    Backbone.Mediator.publish("problem:problem-created", line: @annotation.row, text: @annotation.text) if application.isIPadApp

  isEqual: (problem) ->
    _.all ['row', 'column', 'level', 'column', 'message', 'hint'], (attr) =>
      @[attr] is problem[attr]

  destroy: ->
    @removeMarkerRanges()
    @userCodeProblem.off() if @userCodeProblem

  buildAnnotationFromWebDevError: (error) ->
    {
      row: error.line
      column: error.column
      raw: error.message
      text: error.message
      type: 'error'
      createdBy: 'web-dev-iframe'
    }

  buildAnnotationFromAetherProblem: (aetherProblem) ->
    return unless aetherProblem.range
    text = aetherProblem.message.replace /^Line \d+: /, ''
    start = aetherProblem.range[0]
    {
      row: start.row,
      column: start.col,
      raw: text,
      text: text,
      type: @aetherProblem.level ? 'error'
      createdBy: 'aether'
    }

  buildMarkerRangesFromWebDevError: (error) ->
    lineMarkerRange = new Range error.line, 0, error.line, 1
    lineMarkerRange.start = @ace.getSession().getDocument().createAnchor lineMarkerRange.start
    lineMarkerRange.end = @ace.getSession().getDocument().createAnchor lineMarkerRange.end
    lineMarkerRange.id = @ace.getSession().addMarker lineMarkerRange, 'problem-line', 'fullLine'
    textMarkerRange = undefined # We don't get any per-character info from standard errors
    { lineMarkerRange, textMarkerRange }

  buildMarkerRangesFromAetherProblem: (aetherProblem) ->
    return {} unless aetherProblem.range
    [start, end] = aetherProblem.range
    textClazz = "problem-marker-#{aetherProblem.level}"
    textMarkerRange = new Range start.row, start.col, end.row, end.col
    textMarkerRange.start = @ace.getSession().getDocument().createAnchor textMarkerRange.start
    textMarkerRange.end = @ace.getSession().getDocument().createAnchor textMarkerRange.end
    textMarkerRange.id = @ace.getSession().addMarker textMarkerRange, textClazz, 'text'
    lineClazz = "problem-line"
    lineMarkerRange = new Range start.row, start.col, end.row, end.col
    lineMarkerRange.start = @ace.getSession().getDocument().createAnchor lineMarkerRange.start
    lineMarkerRange.end = @ace.getSession().getDocument().createAnchor lineMarkerRange.end
    lineMarkerRange.id = @ace.getSession().addMarker lineMarkerRange, lineClazz, 'fullLine'
    { lineMarkerRange, textMarkerRange }

  removeMarkerRanges: ->
    if @textMarkerRange
      @ace.getSession().removeMarker @textMarkerRange.id
      @textMarkerRange.start.detach()
      @textMarkerRange.end.detach()
    if @lineMarkerRange
      @ace.getSession().removeMarker @lineMarkerRange.id
      @lineMarkerRange.start.detach()
      @lineMarkerRange.end.detach()

  translate: (msg) ->
    return msg unless msg
    tx = (regex, key) ->
      ki = "esper.#{key}"
      key = $.i18n.t(ki)
      return if key is ki
      msg = msg.replace regex, key

    msg = msg.replace /([A-Za-z]+Error:) \1/, '$1'
    return msg if me.get('preferredLanguage', true) in ['en', 'en-US']
    tx /Line (\d+): /, 'line_no'
    tx /TypeError: /, 'type_error'
    tx /ReferenceError: /, 'reference_error'
    tx /`([a-zA-Z.]+)` is not a function/, 'x_not_a_function'
    tx /Look out for capitalization: `([a-zA-Z.]+)` should be `([a-zA-Z.]+)`./, 'capitalization_issues'
    tx /Look out for spelling issues: did you mean `([a-zA-Z.]+)` instead of `([a-zA-Z.]+)`\?/, 'capitalization_issues'
    tx /Empty ([a-zA-Z]+ statement). Put 4 spaces in front of statements inside the ([a-zA-Z]+ statement)\./, 'py_empty_block'
    tx /Unmatched `(.)`.  Every opening `(.)` needs a closing `(.)` to match it./, 'unmatched_token'
    tx /Unterminated string. Add a matching `"` at the end of your string./, 'unterminated_string'
    msg
